import json
import time
from utils.log_util import logger as log
from minio.commonconfig import Tags, CopySource, REPLACE
from config.env import MinioCfg
from minio.deleteobjects import DeleteObject
from minio import Minio
from urllib.parse import quote
from errors.exception import BaseAPIException
from datetime import datetime, timedelta



class OssBaseHandler:
    __obj = None
    __init_flag = True

    def __new__(cls, *args, **kwargs):
        if cls.__obj is None:
            cls.__obj = object.__new__(cls)
        return cls.__obj

    def __init__(self, dirname: str = '', bucket_name: str | None = None):
        print("MinioCfg:", MinioCfg)
        # 虽然是单例，每次实例初始化可以允许正常执行
        log.info("oss client init...")
        self.minio_url = MinioCfg.minio_url
        self.access_key = MinioCfg.minio_access_key
        self.secret_key = MinioCfg.minio_secret_key
        self.dirname = dirname
        self.bucket_name = bucket_name or 'contract'
        self.result = {'code': 200, 'success': True, 'message': 'OK', 'result': []}
        # 仅首次初始化执行
        if self.__init_flag:
            self.client = None
            self.__init_flag = False
        self.minioInit()

    def minioInit(self):
        # minio 客户端只获取一次
        if not self.client:
            self.client = Minio(self.minio_url, access_key=self.access_key, secret_key=self.secret_key, secure=False)
        # 如何是不存在的库，就新建并设置策略
        if not self.client.bucket_exists(self.bucket_name):
            self.client.make_bucket(self.bucket_name)
            # read-write bucket policy
            policy = {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {"AWS": "*"},
                        "Action": [
                            "s3:GetBucketLocation",
                            "s3:ListBucket",
                            "s3:ListBucketMultipartUploads",
                        ],
                        "Resource": f"arn:aws:s3:::{self.bucket_name}",
                    },
                    {
                        "Effect": "Allow",
                        "Principal": {"AWS": "*"},
                        "Action": [
                            "s3:GetObject",
                            "s3:PutObject",
                            "s3:DeleteObject",
                            "s3:ListMultipartUploadParts",
                            "s3:AbortMultipartUpload",
                        ],
                        "Resource": f"arn:aws:s3:::{self.bucket_name}/*",
                    },
                ],
            }
            self.client.set_bucket_policy(self.bucket_name, json.dumps(policy))
            # print(c.get_bucket_policy('contract'))
            log.info(f"{self.bucket_name} created!")
        else:
            log.info(f"{self.bucket_name} already exists! ")

    def getObject(self, filepath="", isDownload=True) -> dict:
        # Read data from response
        response = self.client.get_object(self.bucket_name, filepath)
        s = self.stats(self.bucket_name, filepath)
        header = {}
        if isDownload:
            filename = filepath.split("/")[-1]
            encoded_filename = quote(filename.encode('utf-8'))
            content_disposition = f"attachment; filename*=UTF-8''{encoded_filename}"
            header.update({'Content-Type': 'application/octet-stream'})
            header.update({f'Content-Disposition': content_disposition})
        else:
            header.update({"Content-Type": s["content_type"]})
            log.info("break point")
        return {"data": response.data, "header": header, "memo": s["memo"], "config": s["config"]}

    @staticmethod
    def is_json(data):
        try:
            res = json.loads(data)
        except ValueError as e:
            log.error(e)
            return None
        return res if type(res) != int else None

    def stats(self, bucket, path):
        try:
            stat = self.client.stat_object(bucket, path)
            memo = stat.metadata.get('X-Amz-Meta-Memo', '')
            config = stat.metadata.get('X-Amz-Meta-Config', '')
            # 数字类型不需要decode
            id_client = stat.metadata.get('X-Amz-Meta-Id_client', ''),

            if memo:
                memo = eval(memo).decode("utf-8")
            if config:
                config = eval(config).decode("utf-8")

        except SyntaxError as e:
            log.info(e)
            raise BaseAPIException(detail=e.msg)
        return {"id_client": id_client, "memo": memo, "config": config, "content_type": stat.content_type}

    def listObjects(self, recursive=True):
        log.info("{0} - {1}".format(self.bucket_name, self.dirname))
        objects = self.client.list_objects(
            self.bucket_name, prefix=self.dirname, recursive=recursive,
        )

        def __minio_time_handle(dt: datetime):
            # 增加8小时；我们东八区，比
            dt = dt + timedelta(hours=8)
            return dt.strftime("%Y-%m-%d %H:%M:%S")

        # return [{**self.stats(self.bucket_name, obj.object_name), "filePath": obj.object_name, "version_id": obj.version_id, "bucketName": self.bucket_name,
        #          "create_time": time.mktime(obj.last_modified.timetuple()), "size": obj.size} for obj in objects]
        return [{**self.stats(self.bucket_name, obj.object_name), "filePath": obj.object_name, "version_id": obj.version_id,
                 "bucketName": self.bucket_name, "create_time": __minio_time_handle(obj.last_modified), "size": obj.size} for obj in objects]

    def putFile(self, filepath):
        # 文件大小限制
        stat = self.client.stat_object(self.bucket_name, filepath)
        if stat.size > MinioCfg.minio_file_limit_size:
            self.result.success = False
            self.result.code = 403
            self.result.message = "文件大小不能250M"
            return
        result = self.client.fput_object(
            self.bucket_name, filepath.split("/")[-1], filepath,
        )
        log.info(
            "created {0} object; etag: {1}, version-id: {2}".format(
                result.object_name, result.etag, result.version_id,
            ),
        )
        return self.result

    def delFiles(self, filePaths):
        if isinstance(filePaths, list):
            delete_object_list = map(
                lambda x: DeleteObject(x),
                filePaths
            )
            hasError = False
            errors = self.client.remove_objects(self.bucket_name, delete_object_list)
            for error in errors:
                print("error occured when deleting object", error)
                hasError = True
            if hasError:
                self.result['message'] = "delete failed"
                self.result['code'] = 500
                self.result['success'] = False
        else:
            self.client.remove_object(self.bucket_name, filePaths)
        return self.result

    def updateMemo(self, object_name, metadata=None):
        try:
            tmp_object_name = object_name + "_update_metadata_tmp"
            tmp_obj_res = self.client.copy_object(
                self.bucket_name, tmp_object_name, CopySource(self.bucket_name, object_name))
            log.info("copy to tmp", self.bucket_name,
                     object_name + "->" + tmp_object_name, tmp_obj_res)
            log.info("tmp to origin")
            # self.client.remove_object(self.bucketName, object_name)
            org_obj_res = self.client.copy_object(
                self.bucket_name, object_name,
                CopySource(self.bucket_name, tmp_object_name),
                metadata=metadata, metadata_directive=REPLACE)
            log.info(org_obj_res)
            self.client.remove_object(self.bucket_name, tmp_object_name)
            log.info("remove tmp-obj")
        except Exception as e:
            print(e)
            self.result['code'] = 500
            self.result['success'] = False
            self.result['message'] = f'error: {e}'
        return self.result
